package com.game.logic.channel;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.game.framework.common.utils.TopicUtil;
import com.game.framework.mqsystem.mq.GameMQTemplate;
import com.game.logic.config.GameServerConfig;
import com.game.framework.message.event.IGameMessage;
import com.game.framework.message.stream.ServiceMessageHeader;
import io.netty.buffer.ByteBuf;
import io.netty.util.concurrent.EventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.util.Collection;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ServerChannelContext {

    // 绑定一个执行任务的线程池
    private EventExecutor executor;

    private String channelId;

    private Object cacheData;
    @Autowired
    private GameMQTemplate gameMQTemplate;

    private GameServerConfig serverConfig;

    private NacosDiscoveryProperties nacosDiscoveryProperties;

    private static final String GatewayTopicPrefix = "gateway";

    public ServerChannelContext(ApplicationContext springContext, EventExecutor executor, String channelId) {
        this.executor = executor;
        this.channelId = channelId;
        this.gameMQTemplate = springContext.getBean(GameMQTemplate.class);
        this.nacosDiscoveryProperties = springContext.getBean(NacosDiscoveryProperties.class);
        this.serverConfig = springContext.getBean(GameServerConfig.class);
    }

    /**
     * 向网关发送消息
     * 
     * @param gameMessage
     */
    public void sendToGateway(IGameMessage gameMessage) {
        ByteBuf byteBuf = gameMessage.write();
        ServiceMessageHeader header = (ServiceMessageHeader)gameMessage.getHeader();
        long userId = header.getUserId();
        byte[] data = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(data);
        try {
            String namespace = nacosDiscoveryProperties.getNamespace();
            String gatewayTopic = TopicUtil.generateTopic(namespace, GatewayTopicPrefix, header.getToGameServerId());
            log.debug("sendToGateway Topic:{}", gatewayTopic);
            this.gameMQTemplate.syncSendOrderly(gatewayTopic, data, userId);
            if(log.isDebugEnabled()) {
            	log.debug(gameMessage.toString());
            }
        } catch (Exception e) {
            log.error("向网关返回消息失败", e);
        }
    }
    //广播给网关的消息应该使用推送
    public void broadcastToGateway(IGameMessage gameMessage, Collection<Long> userIds) {
        ByteBuf byteBuf = gameMessage.write();
        ServiceMessageHeader header = (ServiceMessageHeader)gameMessage.getHeader();
        byte[] data = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(data);
        for (Long userId : userIds) {
            //byte[] dataEncode;
            try {
                header.setUserId(userId.longValue());
                //如果是广播消息，发送到固定的网关topic，这样所有的网关都会收到这个消息，然后判断玩家是否连接的这个网关，如果不是，则丢弃消息
                String gatewayTopic = TopicUtil.generateTopic(this.nacosDiscoveryProperties.getNamespace(), GatewayTopicPrefix);
                log.debug("sendToGateway Topic:{}", gatewayTopic);
                //dataEncode = GameMessageInnerCodec.encodeMessage(header, byteBuf);
                this.gameMQTemplate.syncSendOrderly(gatewayTopic, data, userId);
            } catch (Exception e) {
                log.error("广播消息异常", e);
            }
        }
    }

    /**
     * 添加一个延时执行的任务
     */
    public void addScheduleTask(Runnable task, String taskName, long delay, TimeUnit unit) {
        executor.schedule(() -> {
            try {
                task.run();
            } catch (Throwable e) {
                log.error(" channelId:{} {} 延时任务执行失败", channelId, taskName, e);
            }
        }, delay, unit);
    }

    /**
     * 
     * @Desc 添加固定周期执行的任务，是以上一个任务开始的时间计时，period时间过去后，<br/>
     *       检测上一个任务是否执行完毕，如果上一个任务执行完毕，则当前任务立即执行，<br/>
     *       如果上一个任务没有执行完毕，则需要等上一个任务执行完毕后立即执行
     */
    public void addFixedRateScheduleTask(Runnable task, String taskName, long delay, long period, TimeUnit unit) {
        executor.scheduleAtFixedRate(() -> {
            try {
                task.run();
            } catch (Throwable e) {
                log.error(" channelId:{} {} FixedRate定时任务执行失败", channelId, taskName, e);
            }
        }, delay, period, unit);
    }

    /**
     * 
     * 添加定时任务，任务是以上一个任务结束时开始计时，period时间过去后，立即执行
     */
    public void addWithFixedDelayScheduleTask(Runnable task, String taskName, long delay, long period, TimeUnit unit) {
        executor.scheduleWithFixedDelay(() -> {
            try {
                task.run();
            } catch (Throwable e) {
                log.error(" channelId:{} {} WithFixedDelay任务执行失败", channelId, taskName, e);
            }
        }, delay, period, unit);
    }

    public void submitTask(Runnable task, String taskName) {
        executor.execute(() -> {
            try {
                task.run();
            } catch (Throwable e) {
                log.error(" channelId:{} {} 任务执行失败", channelId, taskName, e);
            }

        });
    }


    public String getChannelId() {
        return channelId;
    }

    public Object getCacheData() {
        return cacheData;
    }

    public void setCacheData(Object cacheData) {
        this.cacheData = cacheData;
    }

    public EventExecutor getExecutor() {
        return executor;
    }

    public void setExecutor(EventExecutor executor) {
        this.executor = executor;
    }

    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }

    public GameMQTemplate getGameMQTemplate() {
        return gameMQTemplate;
    }

    public void setGameMQTemplate(GameMQTemplate gameMQTemplate) {
        this.gameMQTemplate = gameMQTemplate;
    }

    public GameServerConfig getServerConfig() {
        return serverConfig;
    }

    public void setServerConfig(GameServerConfig serverConfig) {
        this.serverConfig = serverConfig;
    }

    public NacosDiscoveryProperties getNacosDiscoveryProperties() {
        return nacosDiscoveryProperties;
    }

    public void setNacosDiscoveryProperties(NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
    }
}
